﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.Serialization;
using ArtMath.Component.Point.Converter;
using ArtMath.Data;
using ArtMath.Core.Data;
using ArtMath.PointCollection;
using ArtMath.Setting;
using ArtMath.Resource;
using ArtMath.Attribute;
using ArtMath.Component.Line;
using ArtMath.Core.Helper;
using ArtMath.Flag;
using ArtMath.CommonType;

namespace ArtMath.Component.Point
{
    /// <summary>
    /// 点基类
    /// </summary>
    [Serializable, TypeConverter(typeof(PointBaseConverter))]
    public abstract class PointBase : ShapeBase
    {
        PadStringX x, y, size, outlineWidth;
        PropertyDisplayer coDisplayer;
        List<Curve> tracks = new List<Curve>();
        public LineBase[] OutHosts;
        PointD _pxy = PointD.Empty;
        /// <summary>
        /// 表示点的最新位置：当点移动时，表示移动过程结束后的目标位置，否则表示点的当前位置（等于Pxy）
        /// </summary>
        public PointD NewPxy;
        ColorX outlineColor, fillColor;
        bool tempMoveLock;
        public PointCollectionsManager BufferedMoveSpace;
        public PointBase(SketchPad pad, string name, ToolTypeName toolType, bool addToList = true) : base(pad, name, toolType, addToList)
        {
            outlineColor = new ColorX(Pad);
            fillColor = new ColorX(Pad);
            X = new PadStringX(Pad);
            Y = new PadStringX(Pad);
            Size = new PadStringX(Pad, Settings.Instance.PointSize);
            OutlineWidth = new PadStringX(Pad, Settings.Instance.PointOutlineWidth);
            TrackWidth.SetExpression(Size.Value);
        }
        public override PointCollectionsManager Skeleton
        {
            get
            {
                return new PointCollectionsManager(new DiscontinuityPoint(ToPointD()));
            }
        }
        public override PointCollectionsManager PSkeleton
        {
            get
            {
                return new PointCollectionsManager(new DiscontinuityPoint(Pxy));
            }
        }
        public override PointCollectionsManager NewSkeleton
        {
            get
            {
                return new PointCollectionsManager(new DiscontinuityPoint(NewXY));
            }
        }
        public override PointCollectionsManager NewPSkeleton
        {
            get
            {
                return new PointCollectionsManager(new DiscontinuityPoint(NewPxy));
            }
        }
        [Browsable(false)]
        public virtual bool CoordinateExpressionContainParameter
        {
            get { return false; }
        }
        [Browsable(false)]
        public PointD Pxy
        {
            get { return _pxy; }
            set
            {
                NewPxy = _pxy = value;
            }
        }
        [Browsable(false)]
        public PointD NewXY
        {
            get { return Pad.PxyToXY(NewPxy); }
        }
        [MultilingualCategory("Appearance"), DefaultValue(typeof(PadStringX), "5"), MultilingualDisplayName("Size")]
        public PadStringX Size
        {
            get { return size; }
            set { size = value; }
        }
        [MultilingualCategory("Appearance"), DefaultValue(typeof(PadStringX), "3"), MultilingualDisplayName("OutlineWidth")]
        public PadStringX OutlineWidth
        {
            get { return outlineWidth; }
            set { outlineWidth = value; }
        }
        [MultilingualCategory("Appearance"), DefaultValue(typeof(Color), "Blue"), MultilingualDisplayName("OutlineColor")]
        public virtual Color OutlineColor
        {
            get { return outlineColor.Color; }
            set { outlineColor.Color = value; }
        }
        [MultilingualCategory("Appearance"), DefaultValue(typeof(Color), "Red"), MultilingualDisplayName("FillColor")]
        public virtual Color FillColor
        {
            get { return fillColor.Color; }
            set { fillColor.Color = value; }
        }
        public virtual PadStringX X
        {
            get { return x; }
            set { x = value; }
        }
        public virtual PadStringX Y
        {
            get { return y; }
            set { y = value; }
        }
        [MultilingualCategory(null, "Sketchpad", "Display"), MultilingualDisplayName(null, "Coordinate", "Displayer")]
        public PropertyDisplayer CoordinateDisplayer
        {
            get { return coDisplayer; }
            set { coDisplayer = value; }
        }
        public override PointD MassPoint
        {
            get { return ToPointD(); }
        }
        [Browsable(false)]
        public bool TempMoveLock
        {
            get { return tempMoveLock; }
            set
            {
                tempMoveLock = value;
                //if (tempMoveLock)
                //{
                //    Pad.TempMoveLockedPoints.Add(this);
                //}
            }
        }
        public override bool Movable
        {
            get
            {
                return ObjectState != ObjectState.Normal || !TempMoveLock;
            }
        }
        public override PointD AnchorPoint(PropertyDisplayer disp)
        {
            return Pxy;
        }
        public override void RefreshPropertyDisplayersText()
        {
            CoordinateDisplayer.RefreshText();
            base.RefreshPropertyDisplayersText();
        }
        public override void RefreshPropertyDisplayersLocation()
        {
            CoordinateDisplayer.RefreshLocation();
            base.RefreshPropertyDisplayersLocation();
        }
        public override bool Track_
        {
            get
            {
                return base.Track_;
            }

            set
            {
                base.Track_ = value;
                if (value)
                {
                    if (tracks.Count == 0 || tracks[tracks.Count - 1].Count > 0)
                    {
                        tracks.Add(new Curve());
                    }
                }
            }
        }
        public override void PaintTrack(Graphics gra)
        {
            if (ShowTrack_)
            {
                for (int i = 0; i < tracks.Count; i++)
                {
                    tracks[i].Paint(gra, TrackPen);
                }
            }
        }
        public override void CheckInRegion(RectangleD rect)
        {
            SetInPicker(rect.Contains(Pxy));
            base.CheckInRegion(rect);
        }
        public override void Paint(Graphics gra)
        {
            if (ObjectState == ObjectState.Normal && Pxy.IsPaintable && Visible)
            {
                gra.DrawPoint(new Pen(StyleState_ == StyleState.Hover || Selected && Pad.UseReversedColor ? outlineColor.ReversedColor : OutlineColor, OutlineWidth.FloatValue),
                    new SolidBrush(StyleState_ == StyleState.Hover || Selected && Pad.UseReversedColor ? fillColor.ReversedColor : FillColor),
                    Pxy, Size.Value);
            }
        }
        public double DistanceTo(PointBase point)
        {
            return ToPointD().DistanceTo(point.ToPointD());
        }
        public double GetSlope(PointBase point)
        {
            return ToPointD().GetSlope(point.ToPointD());
        }
        public double PDistanceTo(PointBase point)
        {
            return Pxy.DistanceTo(point.Pxy);
        }
        public double DistanceTo(StraightLineBase line)
        {
            return ToPointD().DistanceTo(line.A, line.B, line.C);
        }
        public override void ForEachPoint(Predicate<PointBase> act)
        {
            if (act == null) return;
            act(this);
        }
        public override Base[] CheckNearBy(PointD Plocation)
        {
            if (ObjectState == ObjectState.Normal && Visible)
            {
                double dist = Pad.PointSelectorRadius;
                if (dist == 0) dist = Settings.Instance.PointAbsorb;
                if (Plocation.DistanceTo(Pxy) <= Size + OutlineWidth + dist)
                {
                    return new Base[] { this };
                }
            }
            return base.CheckNearBy(Plocation);
        }
        public override void ClearTracks()
        {
            tracks.Clear();
            tracks.Add(new Curve());
            base.ClearTracks();
        }
        public override void AddTracks()
        {
            tracks[tracks.Count - 1].Add(Pxy);
        }
        public PointD ToPointD()
        {
            return new PointD(X.Value, Y.Value);
        }
        public virtual PointD ToMathCoordinate()
        {
            return ToPointD();
        }
        public PointF ToPointF()
        {
            return new PointF(X.FloatValue, Y.FloatValue);
        }
        public override string ToString()
        {
            return X.ToString() + ", " + Y.ToString();
        }
        public virtual void SetLine(int index, LineBase value)
        {
            if (index < OutHosts?.Length)
            {
                OutHosts[index]?.OutChildren_Point.Remove(this);
                OutHosts[index] = value;
                OutHosts[index]?.OutChildren_Point.Add(this);
            }
        }
        public override void Delete(bool recoverable = false)
        {
            base.Delete(recoverable);
            if (!recoverable)
            {
                if (OutHosts != null)
                {
                    for (int i = 0; i < OutHosts.Length; i++)
                    {
                        SetLine(i, null);
                    }
                    OutHosts = null;
                }
            }
        }
        public override bool ContainsMovablePoint
        {
            get
            {
                return Movable;
            }
        }
        public override void Relocate(PointsRelocationReason reason)
        {
            ForceMove(Pad.Coordinate.XYToPxy(ToPointD()));//如果不引发事件，圆将无法变成椭圆
            base.Relocate(reason);
        }
        public override void PaintProperties(Graphics gra)
        {
            if (ObjectState == ObjectState.Normal && Visible)
            {
                CoordinateDisplayer.Paint(gra);
            }
            base.PaintProperties(gra);
        }
        public virtual void ConvertType(ToolTypeName targetType)
        {
            bool allow = true;
            if (ReferenceEquals(Pad.OldCoordinate, Pad.Coordinate))
            {
                AllInHostsIterator((LineBase line) =>
                {
                    if (!line.AllowSwitchPointType(this))
                    {
                        allow = false;
                    }
                    return allow;
                });
            }
            if (!allow) return;
            PointBase targetPoint = null;
            PointD newPxy = Pxy;
            if (Pad.Coordinate == null)
            {
                if (Pad.OldCoordinate != null)
                {
                    newPxy += Pad.OldCoordinate.OriginCoordinate.ToPointD() / Pad.Zoom.ToPointD();
                }
            }
            else
            {
                if (Pad.OldCoordinate == null)
                {
                    newPxy -= Pad.Coordinate.OriginCoordinate.ToPointD() / Pad.Zoom.ToPointD();
                }
            }
            SimulateDeleted(() =>
            {
                switch (targetType)
                {
                    case ToolTypeName.zyd:
                        if (Pad.CoordinateType == CoordinateSystemType.PolarCoordinate)
                        {
                            targetPoint = new PPoint(Pad, Name, false);
                        }
                        else
                        {
                            targetPoint = new RPoint(Pad, Name, false);
                        }
                        break;
                    case ToolTypeName.zbd:
                        if (Pad.CoordinateType == CoordinateSystemType.PolarCoordinate)
                        {
                            targetPoint = new PPoint_Fixed(Pad, Name, false);
                        }
                        else
                        {
                            targetPoint = new RPoint_Fixed(Pad, Name, false);
                        }
                        break;
                }
            });
            targetPoint.SpareCoordinate = newPxy;
            afterGenerateNewType(targetPoint);
        }
        protected void afterGenerateNewType(PointBase pt)
        {
            if (!Pad.TryCreateObject(pt)) return;
            AllInHostsIterator((LineBase line) =>
            {
                line.ChangePoint(this, pt);
                return true;
            });
            if (OutHosts != null)
            {
                if (pt.OutHosts == null)
                {
                    pt.OutHosts = new LineBase[OutHosts.Length];
                    for (int i = 0; i < OutHosts.Length; i++)
                    {
                        pt.SetLine(i, OutHosts[i]);
                    }
                }
                //旧点(this)的OutHosts将在最后的Delete中清空
            }
            for (int i = ObjectParameters.Count - 1; i >= 0; i--)
            {
                ObjectParameters[i].ChangePoint(this, pt);
            }
            for (int i = CalculatedParameters.Count - 1; i >= 0; i--)
            {
                CalculatedParameters[i].ChangePoint(this, pt);
            }
            for (int i = Pad.Animations.Count - 1; i >= 0; i--)
            {
                Pad.Animations[i].ChangePoint(this, pt);
            }
            pt.UseFormat(this);
            pt.LayerItem = LayerItem;
            LayerItem = null;
            if (pt.LayerItem != null)
            {
                pt.LayerItem.LinkedObj = pt;
            }
            pt.recycleItem = RecycleItem;
            recycleItem = null;
            if (pt.RecycleItem != null)
            {
                pt.RecycleItem.Remove();//防止在最后pt.Delete(true)时多次插入到ListView中.
                pt.RecycleItem.Tag = pt;
            }
            switch (ObjectState)
            {
                case ObjectState.Normal:
                    Delete();
                    break;
                case ObjectState.Deleted:
                    Delete();
                    pt.Delete(true);
                    break;
            }
        }
        public override void CoordinateSystemTypeChanged()
        {
            ConvertType(ToolType);//点切换类型就不需要基类的转换，否则出错
        }
        /// <summary>
        /// 强制移动点
        /// </summary>
        /// <param name="location">目标坐标</param>
        /// <param name="updateCoordinate">是否更新坐标表达式</param>
        /// <param name="raiseComponentChangedEvent">是否触发组件改变事件</param>
        public virtual void ForceMove(PointD location, bool raiseComponentChangedEvent = true)
        {
            Pxy = location;
            if (!CoordinateExpressionContainParameter)
            {
                if (Pad.Coordinate == null)
                {
                    X.SetExpression(Pxy.X, false);
                    Y.SetExpression(Pxy.Y, false);
                }
                else
                {
                    PointD tmp = Pad.Coordinate.PxyToXY(Pxy);
                    X.SetExpression(tmp.X, false);
                    Y.SetExpression(tmp.Y, false);
                }
            }
            if (raiseComponentChangedEvent)
            {
                ComponentChanged(this);
            }
            Pad.Canvas.Invalidate();
        }
        public override void RotateAngleChanged(double oldValue)
        {
            base.RotateAngleChanged(oldValue);
            if (Movable)
            {
                PointD dest = ToPointD().RotateAt(RotateCenter.ToPointD(), Pad.GetRad(RotateAngle.Value - oldValue));
                Pad.MovePoint(this, Pad.XYToPxy(dest));
            }
        }
        /// <summary>
        /// 获取外父元件改变时点的目标位置
        /// </summary>
        /// <returns></returns>
        public virtual PointD GetNewLocationAfterOutHostChanged(bool getXY = false)
        {
            if (OutHosts?.Length == 1)
            {
                return OutHosts[0].GetOutFreePointRecommendNewLocation(this, getXY);
            }
            return getXY ? NewXY : NewPxy;
        }
        public override Base CreateMirrorImage(double A, double B, double C)
        {
            return base.CreateMirrorImage(A, B, C);
        }
        protected override HashSet<Base> getRelatedObjects()
        {
            HashSet<Base> set = base.getRelatedObjects();
            if (OutHosts != null)
            {
                set.AddRange(OutHosts);
            }
            return set;
        }
        public override bool PrepareMove(ShapeBase sender = null)
        {
            if (Pad.PointFlags.Add(this))
            {
                //Pad.ProcessingPoint.Push(this);
                List<LineBase> lines = new List<LineBase>();
                AllInHostsIterator((LineBase line) =>
                {
                    if (Pad.LineFlags.Add(line))
                    {
                        line.InternalPrepare(this, sender);
                        lines.Add(line);
                    }
                    return true;
                });
                foreach (LineBase item in lines)
                {
                    item.ExternalPrepare(this, sender);
                }
                //foreach (ComponentManagerBase item in ComponentManagers)
                //{
                //    item.PointMove_Prepare(this);
                //}
                //Pad.ProcessingPoint.Pop();
                return true;
            }
            return base.PrepareMove(sender);
        }
        public override bool CleanMove(ShapeBase sender = null)
        {
            if (Pad.PointFlags.Add(this))
            {
                //Pad.ProcessingPoint.Push(this);
                List<LineBase> lines = new List<LineBase>();
                AllInHostsIterator((LineBase line) =>
                {
                    if (Pad.LineFlags.Add(line))
                    {
                        line.InternalClean(this, sender);
                        lines.Add(line);
                    }
                    return true;
                });
                foreach (LineBase item in lines)
                {
                    item.ExternalClean(this, sender);
                }
                //foreach (ComponentManagerBase item in ComponentManagers)
                //{
                //    item.PointMoved_Clean(this);
                //}
                //Pad.ProcessingPoint.Pop();
                return true;
            }
            return base.CleanMove(sender);
        }
        public virtual PointCollectionsManager GetMoveSpace(Base sender = null)
        {
            PointCollectionsManager mgr = new PointCollectionsManager();
            if (TempMoveLock)
            {
                mgr.AddPointCollection(new DiscontinuityPoint(NewXY));
            }
            else
            {
                if (Flags.Shift && IsTop && Pad.AbsorbedObjects.Contains(this))
                {
                    mgr.AddPointCollection(new Ray() { SourcePoint = PointD.Empty, DirectedAngle = Utility.GetFixedAngle(PointD.Empty.GetAngle(NewXY)) });
                }
                else
                {
                    mgr.AddPointCollection(new Universal());
                }
                //内父元件限制
                AllInHostsIterator((LineBase line) =>
                {
                    mgr = mgr.Intersect(line.GetInPointMovingRange(this, sender));
                    if (mgr.IsEmptySet)
                    {
                        mgr.AddPointCollection(new DiscontinuityPoint(NewXY));
                        return false;
                    }
                    return true;
                });
            }
            return mgr;
        }
        public override object GetCopyInfo()
        {
            return new PointCreatingArgs(ToPointD(), false, ToolType == ToolTypeName.zbd);
        }
        public override void AutoCreate(object createInfo, bool tryOffset = false)
        {
            PointCreatingArgs arg = createInfo as PointCreatingArgs;
            if (arg == null) return;
            PointD[] pts = new PointD[] { arg.Location };
            if (tryOffset)
            {
                Pad.CopyOffset(ToolType, pts);
            }
            arg.Location = pts[0];
            Pad.HostObj = this;
            PointAbsorb bkp = Pad.PointAbsorbMode;
            simulateClick(arg);
            Pad.ClearAbsorbedObjects();
            Pad.PointAbsorbMode = bkp;
        }
        public override void BeforePasting(Base src)
        {
            OrderIndex = Pad.PointOrder++;
            base.BeforePasting(src);
        }
        public override bool LocationUsed(IList<PointD> locations)
        {
            if (locations.Count == 1)
            {
                return locations[0].AlmostEquals(ToPointD(), false);
            }
            return base.LocationUsed(locations);
        }
        public override PropertyDisplayer[] PropertyDisplayersCheckNearBy(PointD Plocation)
        {
            List<PropertyDisplayer> res = new List<PropertyDisplayer>(base.PropertyDisplayersCheckNearBy(Plocation));
            if (CoordinateDisplayer.InRegion(Plocation))
            {
                res.Add(CoordinateDisplayer);
            }
            return res.ToArray();
        }
        public override void Move(PointD location, bool isCompulsive = false, Base sender = null)
        {
            if (Pad.PointFlags.Add(this))
            {
                //Pad.ProcessingPoint.Push(this);
                NewPxy = location;
                if (BufferedMoveSpace == null)
                {
                    BufferedMoveSpace = GetMoveSpace(sender);
                }
                PointD? dest = null;
                if (isCompulsive)
                {
                    if (!BufferedMoveSpace.InPointCollections(NewXY)) throw new PointMovingException(this, Flags.ExceptionShieldMessage);
                }
                else
                {
                    dest = BufferedMoveSpace.GetNearestPoint(NewXY);
                    if (!dest.HasValue) throw new PointMovingException(this, Flags.ExceptionShieldMessage);
                }
                LineBase[] linebkp = new LineBase[Pad.LineFlags.Count];
                Pad.LineFlags.CopyTo(linebkp);
                PointBase[] ptbkp = new PointBase[Pad.PointFlags.Count];// Pad.ProcessingPoint.ToArray();
                Pad.PointFlags.CopyTo(ptbkp);
                HashSet<PointD> failCoordinates = null;
                while (true)
                {
                    if (!isCompulsive)
                    {
                        NewPxy = Pad.XYToPxy(dest.Value);
                    }
                    List<LineBase> lines = new List<LineBase>();
                    try
                    {
                        AllInHostsIterator((LineBase line) =>
                        {
                            if (Pad.LineFlags.Add(line))
                            {
                                line.InternalPointMove(this, sender);
                                lines.Add(line);
                            }
                            return true;
                        });
                        ExternalObjectMove(this, sender);
                        foreach (LineBase line in lines)
                        {
                            line.ExternalObjectMove(this, sender);
                        }
                    }
                    catch (PointMovingException)
                    {
                        if (isCompulsive) throw;
                        //最近点不行，如果是散点集，尝试其他点
                        if (failCoordinates == null)
                        {
                            failCoordinates = new HashSet<PointD>();
                        }
                        failCoordinates.Add(dest.Value);
                        bool changed = false;
                        foreach (PointCollectionBase item in BufferedMoveSpace.PointCollections)
                        {
                            DiscontinuityPoint dp = item as DiscontinuityPoint;
                            if (dp == null) continue;
                            foreach (PointD pt in dp.Points)
                            {
                                if (failCoordinates.Contains(pt)) continue;
                                dest = pt;
                                Pad.LineFlags.Clear();
                                Pad.LineFlags.AddRange(linebkp);
                                Pad.PointFlags.Clear();
                                Pad.PointFlags.AddRange(ptbkp);
                                //Pad.ProcessingPoint.Clear();
                                //for (int i = ptbkp.Length - 1; i >= 0; i--)
                                //{
                                //    Pad.ProcessingPoint.Push(ptbkp[i]);
                                //}
                                changed = true;
                                break;
                            }
                            if (changed) break;
                        }
                        if (changed) continue;
                        throw;
                    }
                    break;
                }
                //Pad.ProcessingPoint.Pop();
            }
            else
            {
                if (isCompulsive && !NewPxy.AlmostEquals(location))
                {
                    throw new PointMovingException(this, string.Format(Resources.Translate("MultipleTargetCannotMove"), Name, Pad.PxyToMath(NewPxy), Pad.PxyToMath(location)));
                }
            }
        }
    }
    public class PointMovingException : Exception
    {
        public ShapeBase FailShape { get; }
        public PointMovingException(ShapeBase failShape, string message) : base(message)
        {
            FailShape = failShape;
        }
        //↓代码分析后的建议代码↓
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue(nameof(FailShape), FailShape);
            base.GetObjectData(info, context);
        }
    }
}
